HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/kevin-demo/wp-content/plugins/allspice/includes/membership-page.php
<?php
// includes/membership-page.php
//
// Native membership landing page. Newly generated pages carry the fully editable Gutenberg
// sales-page pattern (membership-blocks.php); the [allspice_membership] shortcode below is
// kept registered forever for pages generated by earlier plugin versions - those keep
// working untouched. Either way, authentication, products/prices, checkout, entitlement
// refresh and account management all stay in the widget (the buttons are the same
// data-allspice-action hooks / #allspice-* hashes every other membership surface uses).

if (!defined('ABSPATH')) exit;

const ALLSPICE_MEMBERSHIP_PAGE_OPTION = 'allspice_membership_page_id';
const ALLSPICE_CREATE_MEMBERSHIP_PAGE_ACTION = 'allspice_create_membership_page';
const ALLSPICE_REGENERATE_MEMBERSHIP_PAGE_ACTION = 'allspice_regenerate_membership_page';

function allspice_membership_page_slug(): string {
    $slug = sanitize_title((string)apply_filters('allspice_membership_page_slug', 'membership'));
    return $slug !== '' ? $slug : 'membership';
}

function allspice_membership_page_title(): string {
    $title = trim((string)apply_filters('allspice_membership_page_title', 'Membership'));
    return $title !== '' ? $title : 'Membership';
}

/* PHP twin of the widget's benefit labels (raw code title-cased as fallback). */
function allspice_membership_benefit_label(string $code): string {
    $map = [
        'ad_free' => 'Ad-free browsing',
        'gated_recipes' => 'Members-only recipes',
        'recipe_card' => 'Members-only recipe cards',
        'meal_plans' => 'Member meal plans',
        'members_only_content' => 'Members-only articles',
    ];
    $code = trim($code);
    if ($code === '') return '';
    if (isset($map[$code])) return $map[$code];
    return ucwords(str_replace(['_', '-'], ' ', $code));
}

/* ------------------------------------------------------------------------------ shortcode */

add_shortcode('allspice_membership', 'allspice_membership_shortcode');
function allspice_membership_shortcode(): string {
    $m = function_exists('allspice_memberships_config') ? allspice_memberships_config() : [];
    $tp = function_exists('allspice_theme_policies_cache_get') ? allspice_theme_policies_cache_get() : [];
    $policy = isset($tp['policy']) && is_array($tp['policy']) ? $tp['policy'] : [];
    $n = function_exists('allspice_memberships_normalized') ? allspice_memberships_normalized() : null;

    $title = trim((string)($m['name'] ?? 'Membership'));
    if ($title === '') $title = 'Membership';
    $description = trim((string)($m['description'] ?? 'Support the site and unlock members-only content.'));
    $join_label = $n !== null && $n['gate_ui']['join_label'] !== '' ? $n['gate_ui']['join_label'] : 'Become a member';
    $login_label = $n !== null && $n['gate_ui']['login_label'] !== '' ? $n['gate_ui']['login_label'] : 'Already a member? Log in';
    $signups_open = function_exists('allspice_memberships_new_signups_enabled')
        ? allspice_memberships_new_signups_enabled() : true;
    $logo = trim((string)($policy['brand_logo_url'] ?? $policy['brand_logo_square_url'] ?? ''));

    $html = '<div class="allspice-membership-page">';
    if ($logo !== '' && preg_match('#^https?://#i', $logo)) {
        $html .= '<div class="allspice-membership-page__brand"><img src="' . esc_url($logo) . '" alt="" /></div>';
    }
    $html .= '<h2 class="allspice-membership-page__title">' . esc_html($title) . '</h2>';
    $html .= '<p class="allspice-membership-page__copy">' . esc_html($description) . '</p>';
    /* Full normalized benefits list (built-in + custom, all of them - landing page shows
       everything; only gates apply the compact limit). */
    $html .= function_exists('allspice_membership_benefits_html') ? allspice_membership_benefits_html(0) : '';
    $html .= '<div class="allspice-membership-page__actions">';
    if ($signups_open) {
        $html .= '<a class="allspice-recipe-gate__join" href="#allspice-membership" data-allspice-action="open_membership" role="button">' . esc_html($join_label) . '</a>';
    } else {
        /* Closed signups: no active Join - existing members still log in and manage. */
        $html .= '<p class="allspice-recipe-gate__closed">' . esc_html(function_exists('allspice_membership_closed_signups_message') ? allspice_membership_closed_signups_message() : 'Membership is not currently accepting new signups.') . '</p>';
    }
    $html .= '<a class="allspice-recipe-gate__login" href="#allspice-login" data-allspice-action="open_login" role="button">' . esc_html($login_label) . '</a>'
        . '</div>'
        /* Manage is hidden in the INITIAL server HTML; the page bundle reveals it only for a
           verified active member (safe GET summary). Hash link still works if revealed. */
        . '<div class="allspice-membership-page__manage">'
        . '<a href="#allspice-account" data-allspice-action="open_account" role="button" style="display:none">Manage membership</a>'
        . '</div>';
    return $html . '</div>';
}

/* Lightweight responsive styling for the landing wrapper, benefits and actions - rides the
   always-loaded gate stylesheet handle; the page stays a normal editable shortcode page. */
/* Landing styles moved into allspice_gate_css() (membership-gate.php) so the in-body
   optimizer-armor embed covers this page too. */

/* ------------------------------------------------------------------------ admin creation */

/*
 * Content for a freshly generated page: the full editable Gutenberg sales-page pattern
 * (membership-blocks.php). The bare shortcode remains only as a fallback for a WP too old
 * to have blocks - and stays registered forever for pages that already carry it.
 */
function allspice_membership_page_default_content(): string {
    if (function_exists('allspice_membership_sales_page_content') && function_exists('register_block_type')) {
        return allspice_membership_sales_page_content();
    }
    return '[allspice_membership]';
}

/* The saved membership page when it is still a valid, non-trashed page - else null. */
function allspice_membership_page_current() {
    $saved = (int)get_option(ALLSPICE_MEMBERSHIP_PAGE_OPTION, 0);
    if ($saved <= 0) return null;
    $post = get_post($saved);
    if (!$post || $post->post_type !== 'page' || $post->post_status === 'trash') return null;
    return $post;
}

function allspice_membership_page_editor_url(int $page_id): string {
    return admin_url('post.php?post=' . $page_id . '&action=edit');
}

/* Legacy = shortcode-era generated page: contains [allspice_membership] and no block markup.
   Such pages keep working untouched; settings offers Regenerate for an editable version. */
function allspice_membership_page_is_legacy_shortcode(string $content): bool {
    if (strpos($content, '[allspice_membership]') === false) return false;
    return strpos($content, '<!-- wp:') === false;
}

/*
 * Create Membership Page - idempotent, never publishes, never overwrites publisher edits:
 *   1. valid saved page id  -> reuse
 *   2. page at the slug     -> adopt (store its id)
 *   3. otherwise            -> create a DRAFT page with the shortcode
 * The stored id feeds allspice_membership_page_id, which the content-gate exclusion (and
 * the allspice_membership_page_id filter) already honors - this page can never be gated.
 */
function allspice_create_membership_page(bool $force_create = false): array {
    if (!$force_create) {
        $current = allspice_membership_page_current();
        if ($current !== null) {
            return ['ok' => true, 'page_id' => (int)$current->ID, 'created' => false, 'reason' => 'reused_saved_id'];
        }
        $slug = allspice_membership_page_slug();
        $by_slug = function_exists('get_page_by_path') ? get_page_by_path($slug, OBJECT, 'page') : null;
        if ($by_slug && $by_slug->post_status !== 'trash') {
            update_option(ALLSPICE_MEMBERSHIP_PAGE_OPTION, (int)$by_slug->ID, false);
            return ['ok' => true, 'page_id' => (int)$by_slug->ID, 'created' => false, 'reason' => 'adopted_slug_page'];
        }
    }
    $page_id = wp_insert_post([
        'post_type' => 'page',
        'post_status' => 'draft',           // NEVER published automatically
        'post_title' => allspice_membership_page_title(),
        'post_name' => allspice_membership_page_slug(),
        'post_content' => allspice_membership_page_default_content(),
    ], true);
    if (is_wp_error($page_id) || (int)$page_id <= 0) {
        return ['ok' => false, 'page_id' => 0, 'created' => false, 'reason' => 'insert_failed'];
    }
    update_option(ALLSPICE_MEMBERSHIP_PAGE_OPTION, (int)$page_id, false);
    return ['ok' => true, 'page_id' => (int)$page_id, 'created' => true, 'reason' => 'created_draft'];
}

/*
 * Regenerate: the CURRENT generated page goes to Trash (recoverable - publisher edits are
 * never destroyed), then a fresh draft is created from the latest default pattern with
 * current pricing. force_create skips reuse/adopt so the trashed page can't be re-adopted.
 */
function allspice_regenerate_membership_page(): array {
    $current = allspice_membership_page_current();
    if ($current !== null) {
        wp_delete_post((int)$current->ID, false); /* false = Trash, NEVER permanent delete */
    }
    delete_option(ALLSPICE_MEMBERSHIP_PAGE_OPTION);
    $r = allspice_create_membership_page(true);
    $r['trashed_page_id'] = $current !== null ? (int)$current->ID : 0;
    return $r;
}

add_action('admin_post_' . ALLSPICE_CREATE_MEMBERSHIP_PAGE_ACTION, 'allspice_admin_post_create_membership_page');
function allspice_admin_post_create_membership_page(): void {
    if (!current_user_can('manage_options')) wp_die('Forbidden', 403);
    check_admin_referer(ALLSPICE_CREATE_MEMBERSHIP_PAGE_ACTION);
    $r = allspice_create_membership_page();
    if (!empty($r['ok']) && (int)$r['page_id'] > 0) {
        /* Success (created OR reused/adopted) lands the admin straight in the page editor. */
        wp_safe_redirect(allspice_membership_page_editor_url((int)$r['page_id']));
        exit;
    }
    set_transient('allspice_admin_notice', ['msg' => 'Could not create the membership page.', 'type' => 'error'], 30);
    wp_safe_redirect(admin_url('options-general.php?page=' . ALLSPICE_SETTINGS_PAGE));
    exit;
}

add_action('admin_post_' . ALLSPICE_REGENERATE_MEMBERSHIP_PAGE_ACTION, 'allspice_admin_post_regenerate_membership_page');
function allspice_admin_post_regenerate_membership_page(): void {
    if (!current_user_can('manage_options')) wp_die('Forbidden', 403);
    check_admin_referer(ALLSPICE_REGENERATE_MEMBERSHIP_PAGE_ACTION);
    $r = allspice_regenerate_membership_page();
    if (!empty($r['ok']) && (int)$r['page_id'] > 0) {
        wp_safe_redirect(allspice_membership_page_editor_url((int)$r['page_id']));
        exit;
    }
    set_transient('allspice_admin_notice', ['msg' => 'Could not regenerate the membership page.', 'type' => 'error'], 30);
    wp_safe_redirect(admin_url('options-general.php?page=' . ALLSPICE_SETTINGS_PAGE));
    exit;
}